﻿// Copyright (c)  YISH. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.

using System;
using System.Text;
using NSQL.DateFormats;
using NSQL.Expressions;
using NSQL.Expressions.Functions;

namespace NSQL.Generator
{
    public class MySqlGenerator : BaseGenerator, IExpressionVisitor<StringBuilder>
    {
        public static SqlGeneratorSettings DefaultSettings => new SqlGeneratorSettings
        {
            LetterCase = LetterCase.LowerCase,
            DatetimeFormatter = MySQLDateFormatter.Instance,
            BeginDelimiter = '`',
            EndDelimiter = '`',
        };
        public static readonly ISqlGenerator Instance = new MySqlGenerator(DefaultSettings);
        
        private readonly MySQLKeyWords _;

        StringBuilder IFunctionVisitor<StringBuilder>.Visit(IAddIntervalFunction addIntervalFunction)
        {
            var builder = new StringBuilder();
            builder.Append(_.Date).Append('_');

            builder.Append(addIntervalFunction.Interval.Interval > 0 ? _.Add : _.Sub);

            builder.Append('(');
            
            builder.Append(addIntervalFunction.DateTime.Accept(this));

            builder.Append(", ");
            
            builder.Append(_.Interval);
            
            builder.Append(' ').Append(Math.Abs(addIntervalFunction.Interval.Interval)).Append(' ');

            switch (addIntervalFunction.Interval.Type)
            {
                case IntervalType.Years:
                    builder.Append(_.Year);
                    break;
                case IntervalType.Months:
                    builder.Append(_.Month);
                    break;
                case IntervalType.Days:
                    builder.Append(_.Day);
                    break;
                case IntervalType.Hours:
                    builder.Append(_.Hour);
                    break;
                case IntervalType.Minutes:
                    builder.Append(_.Minute);
                    break;
                case IntervalType.Seconds:
                    builder.Append(_.Second);
                    break;
                default:
                    throw new ArgumentOutOfRangeException();
            }
            builder.Append(')');
            return builder;
        }

        
        StringBuilder IFunctionVisitor<StringBuilder>.Visit(IVarianceFunction varianceFunction)
        {
            var builder = new StringBuilder();
            builder.Append(_.Variance);
            builder.Append('(');
            switch (varianceFunction.AggregateType)
            {
                case AggregateType.None:
                    break;
                case AggregateType.All:
                    builder.Append(_.All).Append(' ');
                    break;
                case AggregateType.Distinct:
                    break;
                default:
                    throw new ArgumentOutOfRangeException();
            }
            builder.Append(varianceFunction.Expr.Accept(this));
            builder.Append(')');
            return builder;
        }
        StringBuilder IFunctionVisitor<StringBuilder>.Visit(ICastFunction castFunction)
        {
            var builder = new StringBuilder();
            if (string.Equals(castFunction.TargetType, "text", StringComparison.OrdinalIgnoreCase))
            {
                builder.Append(_.Convert)
                    .Append('(')
                    .Append(castFunction.Expr.Accept(this))
                    .Append(", ")
                    .Append(_.Char)
                    .Append(')');
                return builder;
            }
            if (string.Equals(castFunction.TargetType, "int", StringComparison.OrdinalIgnoreCase))
            {
                builder.Append(_.Convert)
                    .Append('(')
                    .Append(castFunction.Expr.Accept(this))
                    .Append(", ")
                    .Append(castFunction.TargetType)
                    .Append(')');
                return builder;
            }
            if (string.Equals(castFunction.TargetType, "float", StringComparison.OrdinalIgnoreCase)||
                string.Equals(castFunction.TargetType, "numeric", StringComparison.OrdinalIgnoreCase))
            {
                builder.Append(_.Convert)
                    .Append('(')
                    .Append(castFunction.Expr.Accept(this))
                    .Append(", ")
                    .Append(_.Decimal).Append('(').Append(65).Append(", ").Append(38).Append(')')
                    .Append(')');
                return builder;
            }

            var targetType = castFunction.TargetType;
            if (string.Equals(targetType, "timestamp", StringComparison.OrdinalIgnoreCase))
            {
                targetType = "datetime";
            }
            builder.Append(_.Convert);
            builder.Append('(');
            builder.Append(castFunction.Expr.Accept(this));
            builder.Append(", ");
            builder.Append(targetType);
            builder.Append(')');
            return builder;
        }
        StringBuilder IFunctionVisitor<StringBuilder>.Visit(IPercentileFunction expression)
        {
            throw new NotSupportedException();
        }
        protected override StringBuilder FormatPaging(StringBuilder statement, int skip, int limit)
        {
            if (skip <= 0 && limit <= 0) return statement;
            
            statement.Append(' ');
            statement.Append(_.Limit);
            statement.Append(' ');
            statement.Append(skip >= 0 ? skip : 0);
            if (limit <= 0) return statement;
            statement.Append(", ");
            statement.Append(limit);
            return statement;
        }
        
        StringBuilder IFunctionVisitor<StringBuilder>.Visit(INvlFunction nvlFunction)
        {
            var builder = new StringBuilder();
            var hasThen = !(nvlFunction.Then is null);

            if (hasThen)
            {
                builder.Append(_.If);
                builder.Append('(');
                builder.Append(nvlFunction.Expr.Accept(this));
                builder.Append(' ').Append(_.Is);
                builder.Append(' ').Append(_.Null);
                builder.Append(", ").Append(nvlFunction.Default.Accept(this));
                builder.Append(", ").Append(nvlFunction.Then.Accept(this));
                builder.Append(')');
            }
            else
            {
                builder.Append(_.If).Append(_.Null);
                builder.Append('(');
                builder.Append(nvlFunction.Expr.Accept(this));
                builder.Append(", ").Append(nvlFunction.Default.Accept(this));
                builder.Append(')');
            }
            return builder;
        }
        
        StringBuilder IFunctionVisitor<StringBuilder>.Visit(IDiffFunction expression)
        {
            var build = new StringBuilder();
            build.Append(_.Timestamp).Append(_.Diff).Append('(');
            switch (expression.Type)
            {
                case ExtractType.Year:
                    build.Append(_.Year);
                    break;
                case ExtractType.Month:
                    build.Append(_.Month);
                    break;
                case ExtractType.Week:
                    build.Append(_.Week);
                    break;
                case ExtractType.Day:
                    build.Append(_.Day);
                    break;
                case ExtractType.Hour:
                    build.Append(_.Hour);
                    break;
                case ExtractType.Minute:
                    build.Append(_.Minute);
                    break;
                case ExtractType.Second:
                    build.Append(_.Second);
                    break;
                default:
                    throw new ArgumentOutOfRangeException();
            }

            build.Append(", ").Append(expression.Start.Accept(this));
            build.Append(", ").Append(expression.End.Accept(this));
            build.Append(')');
            return build;
        }
        
        StringBuilder IFunctionVisitor<StringBuilder>.Visit(IExtractFunction extractFunction)
        {
            var builder = new StringBuilder();
            switch (extractFunction.Type)
            {
                case ExtractType.Year:
                    builder.Append(_.Year);
                    break;
                case ExtractType.Month:
                    builder.Append(_.Month);
                    break;
                case ExtractType.Day:
                    builder.Append(_.Day);
                    break;
                case ExtractType.Hour:
                    builder.Append(_.Hour);
                    break;
                case ExtractType.Minute:
                    builder.Append(_.Minute);
                    break;
                case ExtractType.Second:
                    builder.Append(_.Second);
                    break;
                case ExtractType.Week:
                    builder.Append(_.Week);
                    break;
                default:
                    throw new ArgumentOutOfRangeException();
            }

            builder.Append('(');
            builder.Append(extractFunction.DateTime.Accept(this));
            builder.Append(')');
            return builder;
        }
        
        
        StringBuilder IFunctionVisitor<StringBuilder>.Visit(ITrimFunction trimFunction)
        {
            var hasTrimExpr = !(trimFunction.TrimExpr is null);

            var  builder =  new StringBuilder();
            if (hasTrimExpr)
            {
                builder.Append(_.Trim);
            }
            else
            {
                switch (trimFunction.Type)
                {
                    case TrimType.Both:
                        builder.Append(_.Trim);
                        break;
                    case TrimType.Leading:
                        builder.Append(_.TrimStart);
                        break;
                    case TrimType.Trailing:
                        builder.Append(_.TrimEnd);
                        break;
                    default:
                        throw new ArgumentOutOfRangeException();
                }
            }
            
            builder.Append('(');


            if (hasTrimExpr)
            {
                switch (trimFunction.Type)
                {
                    case TrimType.Both:
                        builder.Append(_.Both);
                        break;
                    case TrimType.Leading:
                        builder.Append(_.Leading);
                        break;
                    case TrimType.Trailing:
                        builder.Append(_.Trailing);
                        break;
                    default:
                        throw new ArgumentOutOfRangeException();
                }
                
                
                builder.Append(' ').Append(trimFunction.TrimExpr.Accept(this));
                builder.Append(' ').Append(_.From).Append(' ');
            }

            builder.Append(trimFunction.Expr.Accept(this));
            builder.Append(')');
            return builder;
        }
        
        StringBuilder IFunctionVisitor<StringBuilder>.Visit(IFormatFunction expression)
        {
            var builder = new StringBuilder();

            if (expression.IsDateFormat)
            {
                builder.Append(_.Date).Append('_').Append(_.Format);
                builder.Append('(');
                builder.Append(expression.Items[0].Accept(this));
                builder.Append(", ");
                var f = (StringExpression) Settings.DatetimeFormatter.Format(expression.Format.Value);
                builder.Append(f.Accept(this));
                builder.Append(')');
            }
            else
            {
                throw new NotSupportedException();
            }
            return builder;
        }

        public MySqlGenerator(Func<SqlGeneratorSettings, SqlGeneratorSettings> setupAction)
            : this(setupAction(DefaultSettings))
        {
        }
        
        public MySqlGenerator(SqlGeneratorSettings settings) : this(settings, new MySQLKeyWords(settings.LetterCase))
        {
            
        }

        private MySqlGenerator(SqlGeneratorSettings settings, MySQLKeyWords keyWords) : base(settings, keyWords)
        {
            _ = keyWords;
        }
        
        protected class MySQLKeyWords : KeyWords
        {
            public MySQLKeyWords(LetterCase letterCase) : base(letterCase)
            {
            }
            public string If => FormatLetter(nameof(If));
            public string Convert => FormatLetter(nameof(Convert));
            public override string PadLeft => FormatLetter("LPad");
            public override string PadRight => FormatLetter("RPad");
        
            public override string TrimStart => FormatLetter("LTrim");
            public override string TrimEnd => FormatLetter("RTrim");

            public override string Power => FormatLetter("Pow");
            public virtual string Sub => FormatLetter(nameof(Sub));
        }
    }
}